package com.flashextended.utils.graphics.bitmap
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.IBitmapDrawable;
	import flash.filters.ColorMatrixFilter;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import mx.controls.Alert;	
	public class BitmapUtil
	{
		private static const LUMINANCE_R:Number = 0.212671;
		
		private static const LUMINANCE_G:Number = 0.715160;

		private static const LUMINANCE_B:Number = 0.072169;
	
		private static const DELTA_INDEX:Array = [
			0,    0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1,  0.11,
			0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
			0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
			0.44, 0.46, 0.48, 0.5,  0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 
			0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
			1.0,  1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
			1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0,  2.12, 2.25, 
			2.37, 2.50, 2.62, 2.75, 2.87, 3.0,  3.2,  3.4,  3.6,  3.8,
			4.0,  4.3,  4.7,  4.9,  5.0,  5.5,  6.0,  6.5,  6.8,  7.0,
			7.3,  7.5,  7.8,  8.0,  8.4,  8.7,  9.0,  9.4,  9.6,  9.8, 
			10.0
		]
		
		public function BitmapUtil()
		{
			//init
		}
		
		
		public function newBitmapData(w:uint, h:uint, t:Boolean, c:uint):BitmapData{
			var bd:BitmapData = new BitmapData(w, h, t, c);
			return bd;
		}
		public function removeBitmapData(bd:BitmapData):void{
			if (bd) {
				bd.dispose();
			}
		}
		
		public function getScaledBitmapData(data:BitmapData, scale:Number):BitmapData{
			var m:Matrix = new Matrix();
			m.scale(scale, scale);
			var newData:BitmapData = new BitmapData(
					data.width * scale,
					data.height * scale,
					true,
					0x00000000
			);
			newData.draw( data, m );
			data.dispose();
			return newData;
		}
		
		/**
		 * @percent value (0..100) 
		 */ 
		public function resizeByPercent(
				inputBitmap:Bitmap,
				percent:Number):Bitmap{
			
			if(inputBitmap==null || isNaN(percent) || percent < -100 || percent > 1000){
				throw new ArgumentError("Invalid argument percent:"+percent+" :"+inputBitmap);
			}
			
			var width:uint = inputBitmap.width * percent / 100;
			var height:uint = inputBitmap.height * percent / 100;

			var newData:BitmapData = newBitmapData(
					width,
					height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			m.scale(percent / 100, percent / 100);
			newData.draw( inputBitmap.bitmapData, m );
			
			var b:Bitmap = new Bitmap( newData );
			
			return b;
		}
		
		/**
		 * Scale image horizontaly or verticaly
		 * @param value (-100..100)
		 */
		public function squish(
				inputBitmap:Bitmap,
				value:Number,
				part:Number = 50):Bitmap{
			
			if(inputBitmap==null || isNaN(value)){
				throw new ArgumentError("Invalid argument percent:"+value+" :"+inputBitmap);
			}
			
			var width:uint = inputBitmap.width * ((value > 0)?(1 + part * value / 10000):1);
			var height:uint = inputBitmap.height * ((value < 0)?(1 - part * value / 10000):1);

			var newData:BitmapData = newBitmapData(
					width,
					height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			if(value > 0){
				m.scale( 1 + part * value / 10000, 1 );
			} else if(value < 0){
				m.scale( 1, 1 - part * value / 10000 );
			} else {
				m.scale(1, 1);
			}
			
			newData.draw( inputBitmap.bitmapData, m );
			
			var b:Bitmap = new Bitmap( newData );
			
			return b;
		}
		
		/**
		 * Rotate bitmap.
		 * @param value from -360..360 in degree
		 */
		public function rotateByAngle(
				inputBitmap:Bitmap,
				angle:Number):Bitmap{
			
			return new Bitmap( rotateDataByAngle(inputBitmap.bitmapData, angle) );
		}
		
		private function rotateDataByAngle(
				inputData:BitmapData,
				angle:Number):BitmapData{
			
			if(inputData==null || isNaN(angle) || angle < -360 || angle > 360){
				throw new ArgumentError("Invalid argument angle:"+angle);
			}
			
			var radAngle:Number = angle * Math.PI / 180;
			var sin:Number = Math.abs(Math.floor(Math.sin( radAngle )*1000)/1000);
			var cos:Number = Math.abs(Math.floor(Math.cos( radAngle )*1000)/1000);
			
			var width:Number = 
					inputData.height * sin + inputData.width * cos;
			var height:Number = 
					inputData.height * cos + inputData.width * sin;
			
			var newData:BitmapData = newBitmapData(
					width,
					height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			m.translate(-inputData.width/2, -inputData.height/2);
			m.rotate( angle * Math.PI / 180 );
			m.translate(width/2, height/2);
			newData.draw( inputData, m );
			
			return newData;
		}
		
		/**
		 * Simply crop bitmap
		 */
		public function crop(
				inputBitmap:Bitmap,
				x:Number, 
				y:Number, 
				width:Number, 
				height:Number
				):Bitmap{
			
			var newData:BitmapData = newBitmapData(
					width,
					height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			m.translate(-x, -y);
			newData.draw( inputBitmap.bitmapData, m );
			
			var b:Bitmap = new Bitmap( newData );
			
			return b;
			
			return inputBitmap;
		}
		
		/**
		 * Apply brightness
		 * @param value from -100..100
		 */
		public function brightness(
				inputBitmap:Bitmap,
				value:Number
				):Bitmap{
			
			
			var newData:BitmapData = newBitmapData(
					inputBitmap.width,
					inputBitmap.height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			newData.draw( inputBitmap.bitmapData, m );
			
			var f:ColorMatrixFilter = new ColorMatrixFilter( getBrightnessTransform(value) );
			var b:Bitmap = new Bitmap( newData );
			
			b.filters = [f];
			var bdLink:BitmapData = newData;
			var editData:BitmapData = newBitmapData(
					b.width,
					b.height,
					true,
					0xFAFAFA);
			m = new Matrix();
			editData.draw( b, m );
			newData.dispose();
			newData = null;
			b.bitmapData.dispose();
			b = null;
			
			b = new Bitmap( editData );
			
			return b;
		}
		
		/**
		 * Apply contrast
		 * @param value from -100..100
		 */
		public function contrast(
				inputBitmap:Bitmap,
				value:Number
				):Bitmap{
			
			var newData:BitmapData = newBitmapData(
					inputBitmap.width,
					inputBitmap.height,
					true,
					0xFAFAFA);
			var m:Matrix = new Matrix();
			newData.draw( inputBitmap.bitmapData, m );
			
			var f:ColorMatrixFilter = new ColorMatrixFilter( getContrastTransform(value) );
			var b:Bitmap = new Bitmap( newData );
			
			b.filters = [f];
			var editData:BitmapData = newBitmapData(
					b.width,
					b.height,
					true,
					0xFAFAFA);
			m = new Matrix();
			editData.draw( b, m );
			newData.dispose();
			newData = null;
			b.bitmapData.dispose();
			b = null;
			
			b = new Bitmap( editData );
			
			return b;
		}
		
		/**
		 * Apply saturation
		 * @param value from -100..100
		 * @url - Code from http://www.boostworthy.com/code_shared/boostworthy_animation_v2_1.zip
		 */
		public function saturation(
				inputBitmap:Bitmap,
				value:Number):Bitmap{

			var newData:BitmapData = newBitmapData(
					inputBitmap.width,
					inputBitmap.height,
					true,
					0xFAFAFA);
			
			
			var m:Matrix = new Matrix();
			newData.draw( inputBitmap.bitmapData, m );
			
			var f:ColorMatrixFilter = new ColorMatrixFilter( getSaturationTransform(value) );
			var b:Bitmap = new Bitmap( newData );
			
			b.filters = [f];
			var editData:BitmapData = newBitmapData(
					b.width,
					b.height,
					true,
					0xFAFAFA);
			m = new Matrix();
			editData.draw( b, m );
			newData.dispose();
			newData = null;
			b.bitmapData.dispose();
			b = null;
			
			b = new Bitmap( editData );
			
			return b;
		
		}
		
		public function negative(inputBitmap:Bitmap):Bitmap{
			var newData:BitmapData = newBitmapData(
					inputBitmap.width,
					inputBitmap.height,
					true,
					0xFAFAFA);
			
			
			var m:Matrix = new Matrix();
			newData.draw( inputBitmap.bitmapData, m );
			
			var f:ColorMatrixFilter = new ColorMatrixFilter( getNegativeTransform() );
			var b:Bitmap = new Bitmap( newData );
			
			b.filters = [f];
			var editData:BitmapData = newBitmapData(
					b.width,
					b.height,
					true,
					0xFAFAFA);
			m = new Matrix();
			editData.draw( b, m );
			newData.dispose();
			newData = null;
			b.bitmapData.dispose();
			b = null;
			
			b = new Bitmap( editData );
			
			return b;
		}
		
		/**
		 * Apply border to image
		 */
		public function addBorder(
				inputBitmap:Bitmap,
				color:Number=0,
				alpha:Number=1,
				width:Number=5,
				height:Number=5):Bitmap{
			
			var newAlpha:Number = Math.floor(255 * alpha) * 0x1000000;
			
			var newWidth:Number = inputBitmap.width + width * 2;
			var newHeight:Number = inputBitmap.height + height * 2;
			
			var oldData:BitmapData = inputBitmap.bitmapData;
			var newData:BitmapData = new BitmapData(
					newWidth,
					newHeight,
					true,
					newAlpha + color);

			var srcRect:Rectangle = new Rectangle(
					0, 0, 
					inputBitmap.width,
					inputBitmap.height
			);
			var mult:Number = 0x100;
			newData.copyPixels( oldData, srcRect, new Point(width, height) );
			
			inputBitmap.bitmapData.dispose();
			
			return new Bitmap( newData );
		}
		
		public function getUIBitmapData(
				target : IBitmapDrawable, 
				width:Number, 
				height:Number, 
				scale:Number = 1.0, 
				alpha:Number = 1.0) : BitmapData{

			var bd : BitmapData = new BitmapData( width * scale, height * scale, true, 0x00000000 );
			var m:Matrix = new Matrix();
			m.scale( scale, scale );
			var ct:ColorTransform = new ColorTransform(1, 1, 1, alpha);
			bd.draw( target, m, ct );
			return bd;
		}
		
		public function addUIToBitmap(
				inputBitmap:Bitmap,
				target : IBitmapDrawable, 
				width:Number, 
				height:Number, 
				scale:Number, 
				angle:Number, 
				alpha:Number, 
				point:Point,
				bitmapRotation:Number=0):void{
			
			var bd:BitmapData = getUIBitmapData( target, width, height, scale, alpha );
			
			var radAngle:Number = angle * Math.PI / 180;
			
			var sin:Number = Math.abs(Math.floor(Math.sin( radAngle )*1000)/1000);
			var cos:Number = Math.abs(Math.floor(Math.cos( radAngle )*1000)/1000);
			var newWidth:Number = 
					( height * sin + width * cos ) * scale;
			var newHeight:Number = 
					( height * cos + width * sin ) * scale;
			var dx:Number = 0;
			var dy:Number = 0;
			
			if(angle > 360){
				angle = angle % 360;
			} else if(angle < 0){
				angle = 360 + angle % 360;
			}
			
			if(angle > 0 && angle < 90){
				dx = ( height * sin ) * scale;
			} else if(angle==90){
				dx = newWidth;
			} else if(angle > 90 && angle < 180){
				dx = newWidth;
				dy = ( height * sin ) * scale;
			} else if(angle==180){
				dx = newWidth;
				dy = newHeight;
			} else if(angle > 180 && angle < 270){
				dx = ( width * cos ) * scale;
				dy = newHeight;
			} else if(angle==270){
				dy = newHeight;
			} else if(angle>270 && angle<360){
				dy = ( width * cos ) * scale;
			}
			
			point.x -= dx;
			point.y -= dy;
			
			var newData:BitmapData = new BitmapData( newWidth, newHeight, true, 0x00000000 );
			var m:Matrix = new Matrix();
			m.rotate( radAngle );
			m.translate( dx, dy );
			
			newData.draw( bd, m, null, null, null, true );

			bd.dispose();
	
			if(bitmapRotation==0){
				addDataToBitmap( inputBitmap, newData, point );
			} else {
				addDataToRotatedBitmap( inputBitmap, newData, point, bitmapRotation );
			}
		}
		
		public function addDataToBitmap(
				inputBitmap:Bitmap, 
				newPart:BitmapData, 
				destPoint:Point):void{
			
			var srcRect:Rectangle = new Rectangle(
					0, 0,
					newPart.width,
					newPart.height
			);
			
			inputBitmap.bitmapData.copyPixels(
					newPart,
					srcRect,
					destPoint,
					null,
					null,
					true
			);
		}
		
		public function addDataToRotatedBitmap(
				inputBitmap:Bitmap, 
				newPart:BitmapData, 
				destPoint:Point,
				rotation:Number):void{
			
			var srcRect:Rectangle = new Rectangle(
					0, 0,
					newPart.width,
					newPart.height
			);

			var bd:BitmapData = newBitmapData( inputBitmap.width, inputBitmap.height, true, 0xFFFFFF );
			bd.copyPixels(
					newPart,
					srcRect,
					destPoint,
					null,
					null,
					true
			);
			
			bd = rotateDataByAngle( bd, rotation );
			
			var srcRect1:Rectangle = new Rectangle(
					0, 0,
					inputBitmap.width,
					inputBitmap.height
			);
			inputBitmap.bitmapData.copyPixels( bd, srcRect1, new Point(0, 0) );
		}
		
		private function getContrastTransform1(value:Number):Array{
			
			var r:Number = value / 100;
			var g:Number = value / 100;
			var b:Number = value / 100;
			
				g = g || r;
				b = b || r;
				
				r+=1;
				g+=1;
				b+=1;
				
			var mat:Array = new Array(r, 0, 0, 0, 128*(1-r),
							 		  0, g, 0, 0, 128*(1-g),
							 		  0, 0, b, 0, 128*(1-b),
									  0, 0, 0, 1, 0);
				
			return mat;
		}
		
		private function getNegativeTransform():Array{
			return [-1,   0,   0,  0, 255,
				     0,  -1,   0,  0, 255,
					 0,   0,  -1,  0, 255,
					 0,   0,   0,  1,   0]
			
		}
		
		private function getSaturationTransform(value:Number):Array{
			
			var nSaturation:Number = (value) / 100;
			nSaturation += 1;
			
			var nLumR:Number  = (1 - nSaturation) * LUMINANCE_R;
			var nLumG:Number  = (1 - nSaturation) * LUMINANCE_G;
			var nLumB:Number  = (1 - nSaturation) * LUMINANCE_B;
			
			return [nLumR + nSaturation, nLumG,               nLumB,               0, 0, 
					nLumR,               nLumG + nSaturation, nLumB,               0, 0,
					nLumR,               nLumG,               nLumB + nSaturation, 0, 0,
					0,                   0,                   0,                   1, 0];
		}
		
		private function getContrastTransform(value:Number):Array {

			var x:Number;
			if (value<0) {
				x = 127+value/100*127
			} else {
				x = value%1;
				if (x == 0) {
					x = DELTA_INDEX[value];
				} else {
					//x = DELTA_INDEX[(p_val<<0)]; // this is how the IDE does it.
					x = DELTA_INDEX[(value<<0)]*(1-x)+DELTA_INDEX[(value<<0)+1]*x; // use linear interpolation for more granularity.
				}
				x = x*127+127;
			}
			
			return new Array(
				x/127,0,0,0,0.5*(127-x),
				0,x/127,0,0,0.5*(127-x),
				0,0,x/127,0,0.5*(127-x),
				0,0,0,1,0,
				0,0,0,0,1
			);
		}
		
		private function getBrightnessTransform( scale:Number ) : Array
		{
			return new Array (
				1.0,	0.0,	0.0,	0.0,   scale,
		        0.0,	1.0,	0.0,	0.0,   scale,
		        0.0,	0.0,	1.0,	0.0,   scale,
		        0.0,	0.0,	0.0,	1.0,   0
			);
		}
		private function getLuminanceTransform() : Array
		{
			var rwgt:Number = .3086;
			var gwgt:Number = .6094;
			var bwgt:Number = .0820;
			
			return new Array (
				rwgt,   gwgt,   bwgt,   0.0,   0.0,
        		rwgt,   gwgt,   bwgt,   0.0,   0.0,
        		rwgt,   gwgt,   bwgt,   0.0,   0.0,
        		0.0,   	0.0,    0.0,   	1.0,   0.0
			);
		}
		public function cloneBitmap(original:Bitmap):Bitmap {
			
			if(original==null){
				throw new Error("Passed bitmap is null.");
			}
			
			var bitmap:Bitmap;
			try{
    	        bitmap = new Bitmap( original.bitmapData.clone() );
			} catch (e:TypeError){
				trace("ERROR Type error occurs during bitmap convertion.");
				return new Bitmap();
			}
			return bitmap;
        }
	}
}